/**
 ********************************************************************
 * @author roocket
 * @file    codec.c
 * @version V0.0.0
 * @date    2024/3/22
 * @brief   This file is to indicate coder functions. 
 * @attention Code file rules:
 * rule: file encoding use UTF8;
 * rule: max line length 120 characters;
 * rule: line separator \r\n;
 * rule: use clion auto code format tool.
 */
 
#include "codec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

FILE *outputFile = NULL;
struct codec_av_info
    {
    AVFormatContext *av_format_context;
    AVCodecContext *av_codec_context;
    AVCodec *av_codec;
    AVStream *av_stream;
    AVPacket *av_packet;
    AVFrame *av_frame;
    AVCodecParameters *av_codec_parameters;
    int get_video_idx;
    int get_audio_idx;
};

void codec_ffmpeg_init(void)
{
    av_register_all();
}

int codec_ffmpeg_init_av_context(struct codec_av_info *av, const char *url)
{
    int ret = -1;
    int i = 0;
    int  get_video_idx = -1;

    /* init format context */
    av->av_format_context = avformat_alloc_context();
    if (!av->av_format_context)
    return -1;

    ret = avformat_open_input(&av->av_format_context, url, NULL, NULL);
    if (-1 == ret)
        return -1;

    /* get info of stream */
    ret = avformat_find_stream_info(av->av_format_context, NULL);
    if (-1 == ret)
        return -1;

    for (;i < av->av_format_context->nb_streams; i++) {
        if (AVMEDIA_TYPE_VIDEO == av->av_format_context->streams[i]->codecpar->codec_type) {
            get_video_idx = i;
            break;
        }
    }

    if (-1 == get_video_idx)
        return -1;

    av->av_codec_parameters = av->av_format_context->streams[get_video_idx]->codecpar;
    av->get_video_idx = get_video_idx;
    return 0;
}

int codec_ffmpeg_init_decoder(struct codec_av_info *av)
{
    int ret = -1;

    /* find decoder */
    av->av_codec = avcodec_find_decoder(av->av_codec_parameters->codec_id);
    if (!av->av_codec)
        return -1;

    av->av_codec_context = avcodec_alloc_context3(av->av_codec);
    if (!av->av_codec_context)
        return -1;

    ret = avcodec_parameters_to_context(av->av_codec_context, av->av_codec_parameters);
    if (-1 == ret)
        return -1;

    /* open decoder */
    ret = avcodec_open2(av->av_codec_context, av->av_codec, NULL);
    if (-1 == ret)
        return -1;

    return 0;
}

int codec_ffmpeg_get_decoder_frame(struct codec_av_info *av)
{
    int ret = -1;

    av->av_frame = av_frame_alloc();
    if (!av->av_frame)
        return -1;

    av->av_packet = av_packet_alloc();
    av_init_packet(av->av_packet);

    /* read one packet and decoding it */
    ret = av_read_frame(av->av_format_context, av->av_packet);
    if (-1 == ret)
        return -1;

    if (av->get_video_idx != av->av_packet->stream_index)
        return -1;

    /* send packet to decoder */
    avcodec_send_packet(av->av_codec_context, av->av_packet);

    ret = avcodec_receive_frame(av->av_codec_context, av->av_frame);
    if (AVERROR_EOF == ret || AVERROR(EAGAIN) == ret) {
        printf("the more data comes\n");
        return -1;
    }
    return 0;
}

int codec_ffmpeg_scale_pix_fmt(struct codec_av_info *av, unsigned char av_pix_fmt, void **streams, int *len)
{
    struct SwsContext *sws_context = NULL;
    AVCodecContext *av_cdc_ctx = av->av_codec_context;

    sws_context = sws_getContext(av_cdc_ctx->width, av_cdc_ctx->height, av_cdc_ctx->pix_fmt,
                av_cdc_ctx->width, av_cdc_ctx->height, av_pix_fmt,
                SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_context)
        return -1;

    /* make a scale */
    sws_scale(sws_context, (const uint8_t *const *)av->av_frame->data,
    av->av_frame->linesize, 0, av_cdc_ctx->height,
    av->av_frame->data, av->av_frame->linesize);

    if (AV_PIX_FMT_YUV420P == av_pix_fmt) {
        int w = av_cdc_ctx->width;
        int h = av_cdc_ctx->height;
        int l = w * h * 3 /2;

        *streams = (void *)malloc(l);
        if (NULL == *streams)
            return -1;

        memcpy(*streams, av->av_frame->data[0], w * h);
        memcpy(*streams + w * h, av->av_frame->data[1], w * h / 4);
        memcpy(*streams + w * h + w * h / 4 , av->av_frame->data[2], w * h / 4);
        *len = l;
    }

    sws_freeContext(sws_context);
    return 0;
    }

void codec_ffmpeg_free_decoder_frame(struct codec_av_info *av)
{
    av_packet_unref(av->av_packet); 
}

void codec_ffmpeg_del_ctx(struct codec_av_info *av)
{
    av_frame_free(&av->av_frame);
    avcodec_close(av->av_codec_context);
    avformat_close_input(&av->av_format_context);
}


int app_main(int argc, char *argv[]) {
    int ret = -1;
    if (argc != 3) {
        fprintf(stderr, "Usage: %s input_mjpeg_file output_yuv_file\n", argv[0]);
        return 1;
    }

    const char *inputFileName = argv[1];
    const char *outputFileName = argv[2];

    outputFile = fopen(outputFileName, "wb");
    // Open output file
    if (!outputFile) {
        fprintf(stderr, "Error opening output file\n");
        return 1;
    }

    struct codec_av_info av = {};

    codec_ffmpeg_init();
    ret = codec_ffmpeg_init_av_context(&av, inputFileName);
    if (-1 == ret) {
        perror("codec_ffmpeg_init_av_context");
        return -1;
    }

    ret = codec_ffmpeg_init_decoder(&av);
    if (-1 == ret) {
        perror("codec_ffmpeg_init_decoder");
        return -1;
    }

    void *yuv = NULL;
    int len;
    while (1) {
        ret = codec_ffmpeg_get_decoder_frame(&av);
        if (-1 == ret) {
            //perror("codec_ffmpeg_get_decoder_frame");
            break;
        }

        printf("get frame %d\n", ret);
        ret = codec_ffmpeg_scale_pix_fmt(&av, AV_PIX_FMT_YUV420P, &yuv, &len);
        if (-1 == ret) {
            free(yuv);
            break;
        }

        /* test code */
        ret = fwrite(yuv, 1, len, outputFile);  
        printf("It has fwritten %d\n", ret);
        codec_ffmpeg_free_decoder_frame(&av); 
        free(yuv);
        yuv = NULL;
    }

    fclose(outputFile);
    codec_ffmpeg_del_ctx(&av);

    return 0;
}
